﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace OnlineMealOrdering.Common
{
    /// <summary>
    /// ObjectMapper
    /// <!--ObjectMapper<CanvasElement, DesignerCanvas> mapproerty = new ObjectMapper<CanvasElement, DesignerCanvas>(decanvProperty);
    /// mapproerty.MapSameNamingProperties();
    /// mapproerty.ToTarget() as DesignerCanvas-->
    /// </summary>
    /// <typeparam name="TSource"></typeparam>
    /// <typeparam name="TTarget"></typeparam>
    public class ObjectMapper<TSource, TTarget>
        where TSource : new()
        where TTarget : new()
    {

        #region properties

        private TSource _source;
        protected TSource Source
        {
            get { return _source; }
        }

        private TTarget _target;
        protected TTarget Target
        {
            get { return _target; }
        }

        private List<Action> _mappingActions = new List<Action>();
        protected List<Action> MappingActions
        {
            get { return _mappingActions; }
        }

        private static IDictionary<string, PropertyInfo[]> classPropertyInfoDictionary = new Dictionary<string, PropertyInfo[]>();

        private PropertyInfo[] _sourcePropertyInfos = null;
        protected PropertyInfo[] SourcePropertyInfos
        {
            get
            {
                string fullName = _source.GetType().FullName;
                if (classPropertyInfoDictionary.Keys.Contains(fullName))
                {
                    _sourcePropertyInfos = classPropertyInfoDictionary[fullName];
                }
                else
                {
                    _sourcePropertyInfos = _source.GetType().GetProperties();
                    if (_sourcePropertyInfos != null)
                    {
                        classPropertyInfoDictionary[fullName] = _sourcePropertyInfos;
                    }
                }
                return _sourcePropertyInfos;
            }
            set { _sourcePropertyInfos = value; }
        }

        private PropertyInfo[] _targetPropertyInfos;
        protected PropertyInfo[] TargetPropertyInfos
        {
            get
            {
                string fullName = _target.GetType().FullName;
                if (classPropertyInfoDictionary.Keys.Contains(fullName))
                {
                    _targetPropertyInfos = classPropertyInfoDictionary[fullName];
                }
                else
                {
                    _targetPropertyInfos = _target.GetType().GetProperties();
                    if (_targetPropertyInfos != null)
                    {
                        classPropertyInfoDictionary[fullName] = _targetPropertyInfos;
                    }
                }
                return _targetPropertyInfos;
            }
            set { _targetPropertyInfos = value; }
        }

        #endregion

        #region public methods

        public ObjectMapper(TSource source, TTarget target = default(TTarget))
        {
            this._source = source;
            this._target = target == null ? new TTarget() : target;
        }

        /// <summary>
        /// Map the same naming properties between source and target 
        /// </summary>
        /// <returns>this</returns>
        public virtual ObjectMapper<TSource, TTarget> MapSameNamingProperties(bool nestingCopy = false)
        {
            _mappingActions.Add(() => CopySameNamingProperties(nestingCopy));

            return this;
        }

        /// <summary>
        /// Customize mapping action between source and target
        /// </summary>
        /// <param name="mapping">mapping action</param>
        /// <returns>this</returns>
        public ObjectMapper<TSource, TTarget> Map(Action<TSource, TTarget> mapping)
        {
            _mappingActions.Add(() => mapping.Invoke(_source, _target));

            return this;
        }

        /// <summary>
        /// Get the result of mapping actions
        /// </summary>
        /// <returns>Target instance</returns>
        public TTarget ToTarget()
        {
            _mappingActions.ForEach(a => a.Invoke());

            return _target;
        }

        #endregion

        #region private methods

        /// <summary>
        /// Copy the same naming property value between source and target
        /// </summary>
        void CopySameNamingProperties(bool nestingCopy)
        {
            foreach (PropertyInfo targetPropertyInfo in TargetPropertyInfos)
            {
                PropertyInfo sourcePropertyInfo = (from s in SourcePropertyInfos where s.Name == targetPropertyInfo.Name select s).FirstOrDefault();
                if (sourcePropertyInfo != null)
                {
                    dynamic sourceValue = sourcePropertyInfo.GetValue(_source, null);
                    try
                    {
                        if (sourcePropertyInfo.PropertyType.IsClass && targetPropertyInfo.PropertyType.IsClass
                            && (sourcePropertyInfo.PropertyType != targetPropertyInfo.PropertyType)
                            && sourceValue != null)
                        {
                            if (nestingCopy)
                            {//嵌套子对象拷贝
                                var genericType = typeof(ObjectMapper<,>).MakeGenericType(sourcePropertyInfo.PropertyType, targetPropertyInfo.PropertyType);
                                var genericObject = Activator.CreateInstance(genericType, new object[] { sourceValue, null });
                                MethodInfo method_MapSameNamingProperties = genericType.GetMethod("MapSameNamingProperties");
                                method_MapSameNamingProperties.Invoke(genericObject, new object[] { nestingCopy });
                                MethodInfo method_ToTarget = genericType.GetMethod("ToTarget");
                                targetPropertyInfo.SetValue(_target, method_ToTarget.Invoke(genericObject, null), null);
                            }
                        }
                        else
                        {
                            targetPropertyInfo.SetValue(_target, sourceValue, null);
                        }
                    }
                    catch
                    {
                        continue;
                    }
                }
            }
        }

        #endregion
    }
}
